
#include "Ship.h"
#include "Utils/Timer.h"
#include "HUD.h"
#include "World.h"
#include "Flag.h"
#include "Weapons/Blaster.h"
#include "Weapons/Railgun.h"
#include "Weapons/Afterburner.h"
#include "Weapons/Bomb.h"
#include "Settings.h"
#include "GameState.h"
#include "Team.h"
#include "Graphics/TextureManager.h"
#include "Explosion.h"
#include "Engine.h"
#include "Shield.h"

#include <iostream>
#include <cassert>
#include <GL/gl.h>
#include <cmath>


Ship::Ship(Player *controlling_player, Vector2D start_pos) :
    position(start_pos),
    velocity(0.0f, 0.0f),
    controlling_player(controlling_player),
    crosshair_dist(1.0f),
    heading(0.0f, 1.0f),
    rotation(0.0f),
    max_speed(Settings::instance().getFloatValue("ship", "max_speed")),
    acceleration(Settings::instance().getFloatValue("ship", "acceleration")),
    turn_rate(Settings::instance().getFloatValue("ship", "turn_rate")),
    size(Settings::instance().getFloatValue("ship", "size")),
    health(100.0f),
    energy(100.0f),
    is_active(true) {
  
    if(controlling_player->getTeamId() == 0){
        ship_texture = 
            TextureManager::instance().loadTexture(Settings::instance().getStringValue
            ("ship", "blue_ship_sprite"));
    }
    else{
        ship_texture = 
            TextureManager::instance().loadTexture(Settings::instance().getStringValue
            ("ship", "red_ship_sprite"));
    }
 
    weapons_primary.push_back(new Blaster(-1, -1, this));
    weapons_primary.push_back(new Railgun(-1, -1, this));
    weapons_primary.push_back(new Bomb(-1, -1, this));

    selected_weapon_primary = weapons_primary.front();

    weapons_secondary.push_back(new Afterburner(-1, -1, this));
    selected_weapon_secondary = weapons_secondary.front();
    last_switch_time = 0; 

    held_flag = NULL;
    engine = new Engine(this); 
    shield = new Shield(this);
}

Ship::~Ship(){
    std::list<WeaponGenerator*>::iterator wit = weapons_primary.begin();
    while(wit != weapons_primary.end()){
        delete *wit;
        ++wit;
    }

    wit = weapons_secondary.begin();
    while(wit != weapons_secondary.end()){
        delete *wit;
        ++wit;
    }

    std::list<PowerUp*>::iterator pit = active_powerups.begin();
    while(pit != active_powerups.end()){
        delete *pit;
        ++pit;
    }

    delete engine;
    delete shield;
}

void Ship::render(void){
    engine->render();

    glPushMatrix();

    glTranslatef(position.x, position.y, 0.0f);
    glRotatef(rotation, 0.0f, 0.0f, 1.0f);

    shield->render();    

    glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
    glEnable(GL_ALPHA_TEST);
    glAlphaFunc(GL_GREATER, 0.0f);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    TextureManager::instance().bindTexture(ship_texture);

    const float scale = 0.9f;
    glBegin(GL_QUADS);
      glTexCoord2f(0.0f, 1.0f);
      glVertex3f(-size*scale, -size*scale, 0.0f);

      glTexCoord2f(0.0f, 0.0f);
      glVertex3f(-size*scale, size*scale, 0.0f);

      glTexCoord2f(1.0f, 0.0f);
      glVertex3f(size*scale, size*scale, 0.0f);

      glTexCoord2f(1.0f, 1.0f);
      glVertex3f(size*scale, -size*scale, 0.0f);
    glEnd();

    glDisable(GL_BLEND);
    glDisable(GL_ALPHA_TEST);
    glDisable(GL_TEXTURE_2D);    

    glPopMatrix();

    if(controlling_player->getPlayerType() == HUMAN_PLAYER){
        renderCrosshair();
    }
}

void Ship::renderMinimap(void){
    glPushMatrix();
    glColor3f(0.5f, 0.5f, 1.0f); // TODO Get the team color

    glPointSize(3.0f);
    glBegin(GL_POINTS);
      glVertex3f(position.x, position.y, 0.0f);
    glEnd();

    glPopMatrix();
}


void Ship::update(void){
    purgeExpiredPowerUps();
    performCollisionTest();

    engine->update();
    shield->update();

    position.add(Timer::secondsSinceLastFrame() * velocity);

    // Cap the velocity
    if(velocity.length() > max_speed){
        float diff = velocity.length() - max_speed;
        float decay = powf(0.01f, Timer::secondsSinceLastFrame());
        diff *= decay;

        velocity.normalise();
        velocity.scale(max_speed + diff);
    }

    energy += Timer::secondsSinceLastFrame()*40.0f;
    if(energy > 100.0f){ energy = 100.0f; }
}


bool Ship::active(void){
    return is_active;
}

bool Ship::resolveCollision(Ship *ship){
    Vector2D to_ship = ship->getPosition()-position;
    if(ship != this && to_ship.length() < size+ship->getSize()){
        Vector2D midpoint = position + 0.5f*to_ship;
        Vector2D to_ship_norm = to_ship;
        to_ship_norm.normalise();

        ship->setPosition(midpoint + ship->getSize()*to_ship_norm);
        position = midpoint - size*to_ship_norm;

        ship->setVelocity(ship->getVelocity() + 0.3f*to_ship_norm);
        velocity = velocity - 0.3f*to_ship_norm;

//        std::cerr << "Ship :: resolveCollision(ship)" << std::endl;
//        midpoint.printString();
//      filter out collisions that have already been handled?
        impact(midpoint);
        ship->impact(midpoint);

        return true;
    }    
    else{
        return false;
    }
}

bool Ship::resolveCollision(Weapon *){
    return false;
}

bool Ship::resolveCollision(Map *map){
    Vector2D cnormal, cpoint, cfp;

    if(map->collideSphere(position, size, cnormal, cpoint, cfp)){
        Vector2D change = cnormal.dotProduct(velocity)*2.0f * cnormal;
        velocity = velocity - change;
        velocity.scale(0.75f); // TODO i need to do the whole collision thing more elegantly
       
        // Rotate the velocity vector by a small random amount after a collision.
        float random_rot = ((float)rand()/(float)RAND_MAX - 0.5f) * 60.0f;
        velocity.rotate(random_rot);

        position = cfp;
        assert(!map->collideSphere(position, size, cnormal, cpoint, cfp));
       
//        std::cerr << "Ship :: resolveCollision(map)" << std::endl;
//        cpoint.printString();
        impact(cpoint);
 
        return true;
    }

    if(map->clipPointInsideMap(position, cnormal)){
        if(fabs(cnormal.x) < fabs(cnormal.y)){ cnormal = Vector2D(1.0f, 0.0f); }
        else{ cnormal = Vector2D(0.0f, 1.0f); }
        
        velocity.x *= cnormal.x;
        velocity.y *= cnormal.y;
    }    

    return false;
}

void Ship::accelerate(float how_much, bool afterburn){
    engine->accelerate(how_much, afterburn); 
    return;
#if defined(WASD_ABS)
    Vector2D head(0,how_much);
    velocity.add(head * acceleration * Timer::secondsSinceLastFrame());
#elif defined(WASD_REL)
    velocity.add(getHeading() * acceleration * how_much * Timer::secondsSinceLastFrame());
#else
    #error "Define either WASD_REL or WASD_ABS"
#endif
    
    post_accel(how_much);
}

void Ship::post_accel(float how_much) {
    if(how_much > 1.0f){
        if(velocity.length() > max_speed*how_much){
            velocity.normalise();
            velocity.scale(max_speed*how_much);
        }
    }
    else if(velocity.length() > max_speed){
        velocity.normalise();
        velocity.scale(max_speed);
    }
}

void Ship::side_thrust(float how_much){
    engine->side_thrust(how_much);
    return;
#if defined(WASD_ABS)
    Vector2D head(how_much,0);
    velocity.add(head * acceleration * Timer::secondsSinceLastFrame());
#elif defined(WASD_REL)
    Vector2D head = getHeading();
    head.rotate(-90);
    velocity.add(head * acceleration * how_much * Timer::secondsSinceLastFrame());
#else
    #error "Define either WASD_REL or WASD_ABS"
#endif
    post_accel(how_much);
}

void Ship::turn(float direction){
    float turn_amount = turn_rate * Timer::secondsSinceLastFrame();
    if(turn_amount > fabs(direction)){ turn_amount = fabs(direction); }
    if(direction < 0.0f){ turn_amount = -turn_amount; }

    rotation += turn_amount;
    while(rotation >=  360.0f){ rotation -= 360.0f; }
    while(rotation <= -360.0f){ rotation += 360.0f; }

    // Now derive the heading vector based on the rotation value.
    heading.x = sin(-M_PI/180.0f * rotation);
    heading.y = cos(-M_PI/180.0f * rotation);
    heading.normalise();

    
    // Update the position of the targetting crosshair
    float target_dist =  (HUD::instance().getCursorWorldPosition() - position).length();

    // clip it so that it doesnt come too close to the ship
    if(target_dist < 2.0f*size){ target_dist = 2.0f*size; } 
    float to_target = target_dist - crosshair_dist;
    float movement = to_target * (1.0f - fabs(direction)/180.0f);

    crosshair_dist += movement;
    crosshair_pos = position + (heading*crosshair_dist);
}


void Ship::fire(bool primary){
    assert(selected_weapon_primary != NULL);
    assert(selected_weapon_secondary != NULL);

    if(primary){
        selected_weapon_primary->fire();
    } else {
        selected_weapon_secondary->fire();
    }
}


void Ship::switch_weapon(bool primary, bool forward){
    if(Timer::currentTicks() - last_switch_time < 300){ return; }
    last_switch_time = Timer::currentTicks();

    std::list<WeaponGenerator*> *search_list = &weapons_primary;
    if(!primary){ search_list = &weapons_secondary; }

    WeaponGenerator **change_pointer = &selected_weapon_primary;
    if(!primary){ change_pointer = &selected_weapon_secondary; }
 
    
    if(forward){
        if(search_list->back() == *change_pointer){
            *change_pointer = search_list->front();
        }
        else{
            std::list<WeaponGenerator*>::iterator it = search_list->begin();
            while(it != search_list->end() && *it != *change_pointer){ ++it; }
            ++it;
            *change_pointer = *it;
        }
    }
    else{
        if(search_list->front() == *change_pointer){
            *change_pointer = search_list->back();
        }
        else{
            std::list<WeaponGenerator*>::reverse_iterator it = search_list->rbegin();
            while(it != search_list->rend() && *it != *change_pointer){ ++it; }
            ++it;
            *change_pointer = *it;
        }
    }
}


Vector2D Ship::getPosition(void) const {
    return position;
}

Vector2D Ship::getVelocity(void) const {
    return velocity;
}

Vector2D Ship::getCrosshairPos(void) const {
    return crosshair_pos;
}

Vector2D Ship::getHeading(void) const { 
    return heading;
}

float Ship::getRotation(void) const {
    return rotation;
}

float Ship::getSize(void) const {
    return size;
}

int Ship::getHealth(void) const {
    return (int)health;
}

int Ship::getEnergy(void) const {
    return (int)energy;
}

Player* Ship::getControllingPlayer(void) const {
    return controlling_player;
}

void Ship::setPosition(Vector2D p){
    position = p;
}

void Ship::setVelocity(Vector2D v){
    velocity = v;
}

void Ship::addPowerUp(PowerUp *power_up){
    assert(power_up != NULL);
    active_powerups.push_back(power_up);
}

void Ship::grabFlag(Flag *flag){
    
    held_flag = flag;
}

bool Ship::resetHeldFlags(void){
    if(held_flag == NULL){ 
        return false; 
    }
    else{ 
        held_flag->reset();
        held_flag = NULL;
        return true; 
    }
}

bool Ship::haveFlag(void){
    return held_flag != NULL;
}

void Ship::dealDamage(int amount){
    health -= amount;
    if(health < 0.0f){ health = 0.0f; }
    if(health > 100.0f){ health = 100.0f; }
}

void Ship::impact(Vector2D rel_pos){
    shield->energise(rel_pos - position);
}

void Ship::die(void){
    if(!is_active){ return; }

    is_active = false;
    if(held_flag != NULL){
        held_flag->drop();
        held_flag = NULL;
    }

    Explosion *e = new Explosion(50, position, Vector2D(0.0f, 0.0f), 
                                 0.0f, 1.0f,
                                 1.0f, 0.5f, 0.0f, 
                                 0.15f, 2.0f, "data/Cloud.png");
    World::instance().addObject(e);
}

void Ship::drainEnergy(float amount){
    energy -= amount;
    if(energy < 0.0f){ energy = 0.0f; }
    if(energy > 100.0f){ energy = 100.0f; }
}

void Ship::purgeExpiredPowerUps(void){
    std::list<PowerUp*>::iterator it = active_powerups.begin();
    while(it != active_powerups.end()){
        if((*it)->isExpired()){
            
        }
        else{
            ++it;
        }
    }
}

void Ship::renderCrosshair(void){
    glPushMatrix();
    glColor3f(1.0f, 1.0f, 1.0f); // TODO Get the team color

    glTranslatef(crosshair_pos.x, crosshair_pos.y, 0.0f);

    glBegin(GL_POINTS);            
      glVertex3f(0.0f, 0.0f, 0.0f); 
    glEnd();     

    glPopMatrix();
}


void Ship::performCollisionTest(void){
    std::vector<WorldObject *> world_objects = World::instance().getObjectsInVicinity(position);
    for(unsigned i = 0; i < world_objects.size(); i++){
        world_objects[i]->resolveCollision(this);
    }    

}

